﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ecdl.demo.persistence;
using System.Xml.Serialization;
using System.Reflection;

namespace ecdl.demo.model.expression {

    public abstract class ExpressionDTO : DTO<Expression> {
        private Type nativeType;

        public ExpressionDTO() {

        }

        public ExpressionDTO(Type nativeType) {
            this.nativeType = nativeType;
        }

        public Type GetExpressionType() {
            return nativeType;
        }

        public override Expression ToModel(ModelFactory factory) {
            return ToModel(factory, new HashSet<string>());
        }

        public virtual Expression ToModel(ModelFactory factory, IEnumerable<string> ignoreProperties) {
            Expression model = (Expression)factory.CreateModel(GetExpressionType());
            syncIdenticProperties(model, factory, ignoreProperties);
            return model;
        }

        private void syncIdenticProperties(object other, ModelFactory factory, IEnumerable<string> ignoreProperties) {
            var blackList = getBlackList(ignoreProperties, this.GetType());
            foreach (var property in this.GetType().GetProperties().Except(blackList)) {
                var otherProp = other.GetType().GetProperty(property.Name);
                if (otherProp != null) {
                    if (property.PropertyType.IsAssignableFrom(typeof(ExpressionDTO))) {
                        ExpressionDTO dto = (ExpressionDTO)property.GetValue(this, null);
                        otherProp.SetValue(other, dto.ToModel(factory), null);
                    } else {
                        otherProp.SetValue(other, property.GetValue(this, null), null);
                    }
                }
            }
        }

        private IEnumerable<PropertyInfo> getBlackList(IEnumerable<string> ignoreProperties, Type type) {
            var blackList = new HashSet<PropertyInfo>();
            foreach (var propName in ignoreProperties) {
                var prop = type.GetProperty(propName);
                if (prop != null) {
                    blackList.Add(prop);
                }
            }
            return blackList;
        }
    }

    public abstract class SingleExpressionDTO : ExpressionDTO {
        public SingleExpressionDTO(Type nativeType)
            : base(nativeType) {
        }

        public ExpressionDTO Expression { get; set; }

    }

    public abstract class MultiExpressionDTO : ExpressionDTO {

        private static IEnumerable<string> ignoreExpressions = new string[] { "Expressions" };

        public MultiExpressionDTO(Type nativeType)
            : base(nativeType) {
                Expressions = new ExpressionDTO[0];
        }

        //public List<ExpressionDTO> Expressions { get; set; }
        public ExpressionDTO[] Expressions { get; set;  }

        public override Expression ToModel(ModelFactory factory) {
            var model = base.ToModel(factory, ignoreExpressions);

            if (model is CompositeExpression) {
                CompositeExpression expression = (CompositeExpression)model;
                IEnumerable<Expression> expressions = Expressions.Select<ExpressionDTO, Expression>(x => x.ToModel(factory));
                //IEnumerable<Expression> expressions = Expressions.AsEnumerable<ExpressionDTO>().ConvertAll<Expression>(x => x.ToModel(factory));
                expression.Expressions.AddRange(expressions);
            }
            return model;
        }
    }
}
